package com.spudtech.graphics
{
    
    import flash.geom.*;
    import flash.display.*;
    
    import com.spudtech.*;
    import com.spudtech.error.*;
    
    
    public class SpudTilemap extends SpudCanvas
    {
        
        protected var _tileData : Array;
        
        protected var _gridWidth : uint;
        protected var _gridHeight : uint;
        
        protected var _tileWidth : uint;
        protected var _tileHeight : uint;
        
        public function SpudTilemap(source_img : DisplayObject,
                                    grid_size : Point,
                                    tile_size : Point,
                                    tile_data : *)
        {
            super(
                (grid_size.x*tile_size.x),
                (grid_size.y*tile_size.y)
            );
            
            this.graphic = source_img;
            
            
            _gridWidth = grid_size.x;
            _gridHeight = grid_size.y;
            
            _tileWidth = tile_size.x;
            _tileHeight = tile_size.y;
            
            
            _tileData = new Array();
            
            
            if (tile_data is Array)
            {
                if (tile_data.length > 0)
                {
                    if (tile_data[0] is Array)
                    {
                        // 2-dimensional array
                        
                        _tileData = tile_data;
                        
                    }
                    else if (tile_data[0] is int)
                    {
                        for (var row_num : int = 0 ; row_num < grid_size.y ; row_num++)
                        {
                            _tileData.push(new Array());
                            
                            for (var col_num : int = 0 ; col_num < grid_size.x ; col_num++)
                            {
                                var data_index : int = (row_num * grid_size.y) + col_num;
                                
                                if (data_index <= tile_data.length)
                                {
                                    // We've reached the end of the data
                                    break
                                }
                                
                                if (tile_data[data_index] >= 0)
                                {
                                    _tileData[row_num].push(tile_data[data_index]);
                                }
                                else
                                {
                                    _tileData[row_num].push(0);
                                }
                                
                            }
                            
                        }
                    }
                }
                else
                {
                    throw new SpudEmptyDataError("Empty tile data array given");
                }
            }
            
        }
        
        
        public function get tileData() : Array
        {
            return _tileData;
        }
        
        
        public function get gridWidth() : uint
        {
            return _gridWidth;
        }
        public function set gridWidth(new_width : uint) : void
        {
            _gridWidth = new_width;
        }
        
        public function get gridHeight() : uint
        {
            return _gridHeight;
        }
        public function set gridHeight(new_height : uint) : void
        {
            _gridHeight = new_height;
        }
        
        public function get tileWidth() : uint
        {
            return _tileWidth;
        }
        public function set tileWidth(new_width : uint) : void
        {
            _tileWidth = new_width;
        }
        
        public function get tileHeight() : uint
        {
            return _tileHeight;
        }
        public function set tileHeight(new_height : uint) : void
        {
            _tileHeight = new_height;
        }
        
        
        public function setTile(position : Point, tile_index : uint) : void
        {
            if ((position.x < 0 || position.y < 0) ||
                position.x > gridWidth ||
                position.y > gridHeight)
            {
                throw new RangeError("Point is outside of grid");
            }
            
            tileData[position.y][position.x] = tile_index;
            
        }
        
        public function setTileList(positions : Array, tile_index : uint) : void
        {
            // Filter out non-points
            var is_point : Function = function(item : *, index : int, array : Array) : Boolean
            {
                if (item is Point)
                {
                    // It is a flash.geom.Point
                    // That works just fine
                    return true;
                }
                else if (item is Array)
                {
                    // It is an Array
                    // If we have two ints it is a good point
                    
                    if (item.length != 2)
                    {
                        // There aren't two elements
                        // More, less, it doesn't matter
                        // It's not a valid point
                        return false;
                    }
                    else
                    {
                        // There are two elements
                        
                        if (!(item[0] is int || item[1] is int))
                        {
                            // One item or the other
                            // is not an int
                            return false;
                        }
                        else
                        {
                            // Both items are ints
                            return true;
                            
                        }
                        
                    }
                }
                else
                {
                    // It is neither a flash.geom.Point
                    // or an Array
                    return false;
                }
            }
            
            for each (var point : Object in positions.filter(is_point))
            {
                if (point is Point)
                {
                    // The point is a flash.geom.Point
                    setTile(Point(point),tile_index);
                }
                else
                {
                    // Because we filtered out bad
                    // points above, we can just
                    // assume that this is a
                    // two-int array
                    
                    // Set a tile using a new Point
                    // made with the first and then
                    // second element from the
                    // given point
                    
                    setTile(
                        new Point(
                            point[0],
                            point[1]),
                        tile_index
                    );
                }
                
            }
            
        }
        
        public function setTileRegion(start_x : uint, start_y : uint,
                                     width : uint, height : uint,
                                     tile_index : uint) : void
        {
            var points : Array = new Array();
            
            if (start_x + width > gridWidth ||
                start_y + height > gridHeight)
            {
                throw new RangeError("Region is not completely within grid");
            }
            
            for (var x_mod : int = 0 ; x_mod < width ; x_mod++)
            {
                // 'x_mod' is added to start_x
                
                for (var y_mod : int = 0 ; y_mod < height ; y_mod++)
                {
                    // 'y_mod' is added to start_y
                    
                    points.push(new Point((start_x+x_mod),(start_y+y_mod)));
                    
                }
                
            }
            
            setTileList(points,tile_index);
            
        }
        
        public function setTileRect(area : Rectangle, tile_index : uint) : void
        {
            setTileRegion(
                area.left,
                area.top,
                area.width,
                area.height,
                tile_index
            );
            
        }
        
        
        public function getTile(position : Point) : int
        {
            if ((position.x < 0 || position.y < 0) ||
                (position.x > gridWidth ||
                 position.y > gridHeight))
            {
                throw new RangeError("Point is outside of grid bounds");
            }
            
            position.x = Math.round(position.x);
            position.y = Math.round(position.y);
            
            return tileData[position.y][position.x];
            //return tileData[1][1];
            
        }
        
        public function getTileList(positions : Array) : Array
        {
            var tiles : Array = new Array();
            
            // We only want points
            var is_point : Function = function(item : *, index : int, array : Array) : Boolean
            {
                if (item is Point)
                {
                    var position : Point = Point(item);
                    
                    if (!(position.x is int && position.y is int))
                    {
                        // It's a flash.geom.Point
                        // and both coordinates are
                        // integers
                        return true;
                    }
                    else
                    {
                        // It's a flash.geom.Point
                        // but one coordinate or
                        // the other is not an
                        // integer
                        return false;
                    }
                }
                else if (item is Array)
                {
                    // It's an Array
                    
                    if (item.length != 2)
                    {
                        // It doesn't have 2 elements
                        // It's not a valid point
                        return false;
                    }
                    else if (!(item[0] is int && item[1] is int))
                    {
                        // One or the other is not an
                        // int, so we're fine
                        return false;
                    }
                    else
                    {
                        // It has 2 elements
                        // Both are integers
                        // We can accept that
                        return true;
                    }
                    
                }
                else
                {
                    // It is neither a flash.geom.Point
                    // or an Array
                    // It's definitely not a point
                    return false;
                }
            }
            
            
            for each (var point : * in positions.filter(is_point))
            {
                if (point is Point)
                {
                    // The point is a flash.geom.Point
                    // Cast the point into a
                    // flash.geom.Point and
                    // push the tile at that
                    // point into the tile
                    // index array
                    tiles.push(getTile(Point(point)));
                }
                else
                {
                    // It is a 2-point Array
                    // Construct a new Point
                    // with the given point's
                    // two coordinates and
                    // push the tile at that
                    // point into the tile
                    // index array
                    tiles.push(getTile(
                        new Point(point[0], point[1])
                    ));
                    
                }
            }
            
            //return [3,2,1,0];
            return tiles;
            
        }
        
        public function getTileRegion(start_x : uint,
                                      start_y : uint,
                                      width : uint,
                                      height : uint) : Array
        {
            // The tile data
            var tiles : Array = new Array();
            
            // Each row in the region
            var row_tiles : Array;
            
            for (var row_mod : int = 0 ; row_mod < height ; row_mod++)
            {
                // 'row_mod' is the number of rows
                // we are below the start row
                
                row_tiles = new Array();
                
                for (var col_mod : int = 0; col_mod < width ; col_mod++)
                {
                    // Push on a new point for each
                    // point in that row
                    
                    row_tiles.push(
                        getTile(
                            new Point(
                                (start_x+col_mod),
                                (start_y+row_mod)
                            )
                        )
                    );
                }
                
                
                // We push the row onto the Array
                // of tile indexes
                tiles.push(row_tiles);
                
            }
            
            return tiles;
            
        }
        
        public function getTileRect(area : Rectangle) : Array
        {
            return getTileRegion(area.x,area.y,area.width,area.height);
        }
        
        
        public function renderTile(grid_offset : Matrix,
                                   grid_point : Point,
                                   tile_index : uint,
                                   draw_graphics : Graphics) : void
        {
            if ((grid_point.x < 0 || grid_point.y < 0) ||
                (grid_point.x > gridWidth ||
                 grid_point.y > gridHeight))
            {
                throw new RangeError("Grid point is outside of grid bounds");
            }
            
            var tile_clip_rect : Rectangle = new Rectangle(0,0,tileWidth,tileHeight);
            
            var img_grid_width : int = Math.round(this.graphic.width / tileWidth);
            var img_grid_height : int = Math.round(this.graphic.height / tileHeight);
            
            tile_clip_rect.x = tile_index % img_grid_width;
            tile_clip_rect.y = Math.floor(tile_index / img_grid_width);
            
            tile_clip_rect.x *= tileWidth;
            tile_clip_rect.y *= tileHeight;
            
            var tile_matrix : Matrix = grid_offset.clone();
            
            tile_matrix.translate(
                (grid_point.x * tileWidth),
                (grid_point.y * tileHeight)
            );
            
            super.render(
                    tile_matrix,
                    tile_clip_rect,
                    draw_graphics
            );
            
        }
        
        
        override public function render(offset : Matrix,
                                        clip_rect : Rectangle,
                                        draw_graphics : Graphics) : void
        {
            // The area of the tilemap
            // that will be rendered
            var render_region : Rectangle = new Rectangle(0,0,0,0);
            
            // Get the size of the render region
            render_region.width = Math.round(clip_rect.width / tileWidth);
            render_region.height = Math.round(clip_rect.height / tileHeight);
            
            // Get the position of the render region
            render_region.x = Math.round(clip_rect.x / tileWidth);
            render_region.y = Math.round(clip_rect.y / tileHeight);
            
            // Expand for partial tiles
            render_region.left    -= 1;
            render_region.top     -= 1
            render_region.right   += 1;
            render_region.bottom  += 1;
            
            if (render_region.left < 0)
            {
                render_region.left = 0;
            }
            if (render_region.top < 0)
            {
                render_region.top = 0;
            }
            if (render_region.right > gridWidth)
            {
                render_region.right = gridWidth;
            }
            if (render_region.bottom > gridHeight)
            {
                render_region.bottom = gridHeight;
            }
            
            var tiles : Array = getTileRect(render_region);
            
            for (var row_num : int = 0 ; row_num < render_region.height ; row_num++)
            {
                
                for (var col_num : int = 0 ; col_num < render_region.width ; col_num++)
                {
                    renderTile(
                            offset,
                            new Point(
                                (render_region.left + col_num),
                                (render_region.top + row_num)
                            ),
                            tiles[row_num][col_num],
                            draw_graphics
                    );
                    
                }
                
            }
            
        }
        
    }
    
}
